/**
    path.c - Functions for path manipulation.
    Copyright (C) 2009  Abram Magner

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <assert.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>

#include "common.h"
#include "strfuncs.h"
#include "path.h"

//#define PATH_DEBUG



//=============================================================================
// Given path, return its fully expanded form.
// TODO: Implement me!
//=============================================================================
char *path_full (char *relpath)
{
  int relindex = 0, rellen = 0;
  char *current_path = NULL;              // The current path relative to which
                                          // "." and ".." are interpreted.

  if (relpath[0] != '/')
    {
      if ((current_path = malloc (256)) == NULL) return (NULL);
      getcwd (current_path, 256);
    }
  else if ((current_path = strdup ("/")) == NULL) return (NULL);

  // For each chunk, do the following:
  //
  // If the chunk is nothing (i.e., the next char is a slash), skip it.
  // If it's ".", skip it.
  // If it's "..", backtrack in current_path.
  // Otherwise, append to current_path.
  rellen = strlen (relpath);
  while (relindex < rellen)
    {
      char *stringptr = NULL;         // For allocation and temporary storage.
      int chunk_end_index = 0;
      switch (relpath[relindex])
        {
	  case '.':
	    relindex++;
	    if (relpath[relindex] == '.' && (relpath[relindex + 1] == '/' || relpath[relindex + 1] == '\0'))
	      {
                relindex += 2;
		// Backtrack.
		if (strcmp (current_path, "/") == 0)
		  {
		    // If we can't backtrack, this is an error.
		    free (current_path);
		    return (NULL);
		  }
		if (current_path[strlen (current_path) - 1] == '/')
		  current_path[strlen (current_path) - 1] = '\0';
		stringptr = path_tail (current_path);
		fprintf (stderr, "stringptr = %s\n", stringptr);
		free (current_path);
		if (stringptr == NULL) return (NULL);
		current_path = stringptr;
	      }
	    else if (relpath[relindex] == '/' || relpath[relindex] == '\0') relindex++;
	    else
	      {
		// Append.
		if (current_path[strlen (current_path) - 1] != '/')
		  current_path = mstrconcat (current_path, "/");
                chunk_end_index = relindex;
		while (chunk_end_index < rellen && relpath[chunk_end_index] != '/')
		  chunk_end_index++;
		// Get the substring [relindex - 1, chunk_end_index].
		stringptr = substring (relpath, relindex - 1, chunk_end_index - relindex + 2);
		if (stringptr == NULL)
		  {
		    if (current_path != NULL) free (current_path);
		    return (NULL);
		  }
		current_path = mstrconcat (current_path, stringptr);
		free (stringptr);
		relindex = chunk_end_index + 1;
	      }
	  break;
	  case '/':
	    // Skip.
	    relindex++;
	  break;
	  default:
	    // Append.
	    if (current_path[strlen (current_path) - 1] != '/')
	      current_path = mstrconcat (current_path, "/");
	    chunk_end_index = relindex;
	    while (chunk_end_index < rellen && relpath[chunk_end_index] != '/')
	      chunk_end_index++;
	    // Get the substring [relindex, chunk_end_index]
	    stringptr = substring (relpath, relindex, chunk_end_index - relindex + 1);
	    if (stringptr == NULL)
	      {
		if (current_path != NULL) free (current_path);
		return (NULL);
	      }
	    current_path = mstrconcat (current_path, stringptr);
	    free (stringptr);
	    relindex = chunk_end_index + 1;
	  break;
	}
    }
  if (current_path[strlen (current_path) - 1] == '/')
    current_path[strlen (current_path) - 1] = '\0';
  return (current_path);
}

//=============================================================================
// Return path + / + head.
//=============================================================================
char *path_append_head (char *path, char *head)
{
  char *result = NULL;

  if (path == NULL && head == NULL) return (NULL);
  if (path == NULL) return (strdup (head));
  if (head == NULL) return (strdup (path));

  if (path[strlen (path) - 1] != '/') result = strconcat (path, "/");
  else result = strdup (path);
  result = mstrconcat (result, head);
  return (result);
}

//=============================================================================
// Given a canonicalized path, return its last element (i.e., its head).
//=============================================================================
char *path_head (char *path)
{
  char *retval = NULL;
  int index = 0, len = strlen (path);

  for (index = len - 1; index >= 0 && path[index] != '/'; index--);
  if (index < 0) return (strdup (path));	// The path is only a head.
  retval = strdup (&path[index + 1]);
  return (retval);
}

//=============================================================================
// Given a canonicalized path, return its first element (i.e., its root).
//=============================================================================
char *path_root (char *path)
{
  char *retval = NULL;
  int index = 0, len = strlen (path);
  
  for (index = 0; index < len && path[index] != '/'; index++);
  if (index >= len) return (strdup (path));
  path[index] = '\0';
  retval = strdup (path);
  path[index] = '/';
  return (retval);
}

//=============================================================================
// Given a canonicalized path, return the canonicalized path resulting from
// the removal of its head (i.e., its tail).
//=============================================================================
char *path_tail (char *path)
{
  char *retval = NULL;
  int index = 0, len = strlen (path);

  for (index = len - 1; index >= 0 && path[index] != '/'; index--);
  if (index < 0) return (strdup (""));	// The tail is nothing.
  path[index] = '\0';
  retval = strdup (path);
  path[index] = '/';
  return (retval);
}

//=============================================================================
// Given a canonicalized path, return the canonicalized path resulting from
// the removal of its root.
//=============================================================================
char *path_next_root (char *path)
{
  char *retval = NULL;
  int index = 0, len = strlen (path);

  for (index = 0; index < len && path[index] != '/'; index++);
  if (index >= len) return (strdup (""));
  retval = strdup (&path[index + 1]);
  return (retval);
}

//=============================================================================
// Determine whether or not the given path exists in reality.
//=============================================================================
int path_exists (char *path)
{
  struct stat buf;
  return (!(stat (path, &buf) < 0 && errno == ENOENT));
}


//=============================================================================
// Express path in relation to relto.  Both of these should be absolute.
//=============================================================================
char *path_getrelative (char *path, char *relto)
{
  char *result = NULL;
  char *pathmod = NULL, *reltomod = NULL;
  char *path_iter = NULL, *relto_iter = NULL;
  char *curpath = NULL, *currelto = NULL;

  if ((pathmod = strdup (path)) == NULL) return (NULL);
  if ((reltomod = strdup (relto)) == NULL)
    {
      free (pathmod);
      return (NULL);
    }
  // Find the split point (only one of these).
  curpath = strtok_r (pathmod, "/", &path_iter);
  currelto = strtok_r (reltomod, "/", &relto_iter);
  while (curpath != NULL && currelto != NULL && strcmp (curpath, currelto) == 0)
    {
      curpath = strtok_r (NULL, "/", &path_iter);
      currelto = strtok_r (NULL, "/", &relto_iter);
    }

  // If curpath and currelto aren't both null, we've reached the split point.
  if (curpath == NULL && currelto == NULL)
    {
      free (pathmod);
      free (reltomod);
      return (strdup (""));
    }  

  // Backtrack with the appropriate number of "../" s.
  while (currelto != NULL)
    {
      result = mstrconcat (result, "../");
      currelto = strtok_r (NULL, "/", &relto_iter);
    }
  while (curpath != NULL)
    {
      result = mstrconcat (result, curpath);
      curpath = strtok_r (NULL, "/", &path_iter);
      if (curpath != NULL) result = mstrconcat (result, "/");
    }

  free (pathmod);
  free (reltomod);
  return (result);
}

//=============================================================================
//=============================================================================
int path_isdir (char *fullpath)
{
  struct stat statbuf;
  if (stat (fullpath, &statbuf) < 0) return (0);
  return (S_ISDIR (statbuf.st_mode));
}

//=============================================================================
//=============================================================================
int path_isreg (char *fullpath)
{
  struct stat statbuf;
  if (stat (fullpath, &statbuf) < 0) return (0);
  return (S_ISREG (statbuf.st_mode));
}


//=============================================================================
// Remove path's head, free path, and return the result.
//=============================================================================
char *path_mtail (char *path)
{
  char *tail = NULL;
  if (path == NULL) return (strdup (""));
  tail = path_tail (path);
  free (path);
  return (tail);
}

#ifdef PATH_DEBUG
int main (int argc, char **argv)
{
  char path[] = "usr/brewbot/grah";
  char *root = NULL, *head = NULL, *curpath = NULL;

  curpath = strdup (path);
  head = path_head (curpath);
  assert (strcmp (head, "grah") == 0);

  root = path_root (curpath);
  assert (strcmp (root, "usr") == 0);

  curpath = path_next_root (curpath);
  assert (strcmp (curpath, "brewbot/grah") == 0);

  head = path_head (curpath);
  root = path_root (curpath);
  assert (strcmp (head, "grah") == 0);
  assert (strcmp (root, "brewbot") == 0);

  curpath = path_next_root (curpath);
  assert (strcmp (curpath, "grah") == 0);
  head = path_head (curpath);
  root = path_root (curpath);
  assert (strcmp (head, "grah") == 0);
  assert (strcmp (root, "grah") == 0);

  curpath = path_next_root (curpath);
  assert (strcmp (curpath, "") == 0);
  head = path_head (curpath);
  root = path_root (curpath);
  assert (strcmp (head, "") == 0);
  assert (strcmp (root, "") == 0);

  assert (strcmp (path_getrelative ("/", "/"), "") == 0);
  assert (strcmp (path_getrelative ("/home/abram/grah", "/home/abram/"), "grah") == 0);
  assert (strcmp (path_getrelative ("/", "/home/abram/grah"), "../../../") == 0);

  fprintf (stderr, "path tests passed.\n");
  return (0);
}
#endif
